/*
 * serial-mhn.c: PXA250/210 serial port driver
 *
 * Copyright (C) 2002  Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
 * Copyright (C) 2002  Jeff Sutherland <jeffs@accelent.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <arch.h>
#include <errno.h>
#include <serial.h>
#include <types.h>
#include <time.h>
/* number of nibbles in a word */
#define NIBBLES_PER_WORD (8)
#define NIBBLES_PER_HALFWORD (4)

#if defined USE_SERIAL1

# define SerialDATA  FFRBR
# define SerialFIFO  FFFCR
# define SerialLCR   FFLCR
# define SerialMCR   FFMCR
# define SerialLSR   FFLSR
# define SerialMSR   FFMSR
# define SerialIER   FFIER

#else /* defined USE_SERIAL1 */

#if defined USE_SERIAL2

# define SerialDATA  BTRBR
# define SerialFIFO  BTFCR
# define SerialLCR   BTLCR
# define SerialMCR   BTMCR
# define SerialLSR   BTLSR
# define SerialIER   BTIER
# define SerialMSR   BTMSR

#else /* defined USE_SERIAL2 */

#if defined USE_SERIAL3

/* using an Xscale CPU and not having any serial port defined is an
 * error, but because we're using the driver in the library we can't
 * bail out over here
 */

# define SerialDATA  STRBR
# define SerialFIFO  STFCR
# define SerialLCR   STLCR
# define SerialMCR   STMCR
# define SerialLSR   STLSR
# define SerialIER   STIER
# define SerialMSR   STMSR

#else
#error "No serial port defined USE_SERIAL1 .. USE_SERIAL3"

#endif /* defined USE_SERIAL3 */

#endif /* defined USE_SERIAL2 */

#endif /* defined USE_SERIAL1 */


/* initialise serial port at the request baudrate. returns 0 on
 * success, or a negative error number otherwise
 */
 
int mhn_serial_init(serial_baud_t baud)
{
	u32 divisor;

	*(unsigned int *)GPIO109_MFPR = 0xC041; /* STTXD */
	*(unsigned int *)GPIO110_MFPR = 0x4041; /* STRXD */

	/* enab le clock */
	CKENA |= CKENA_23_STUART;
 
	switch(baud) {
	case baud_1200:
		divisor = 768;
		break;
 
	case baud_9600:
		divisor = 96;
		break;

	case baud_19200:
		divisor = 48;
		break;

	case baud_38400:
		divisor = 24;
		break;

	case baud_57600:
		divisor = 16;
		break;

	case baud_115200:
		divisor = 8;
		break;

	default:
		return -ERANGE;
	}

	/* switch receiver and transmitter off */
	SerialLCR = 0;
	SerialIER = 0;
	SerialFIFO = 0;
	
	/* Gain access to divisor latch */
	SerialLCR = LCR_WLS0 | LCR_WLS1 | LCR_DLAB;

	/* Load baud rate divisor in two steps, lsb, then msb of value */

	SerialDATA = divisor & 0xff;
	SerialIER = (divisor >> 8) & 0xff;
	

	/* set the port to sensible defaults (no break, no interrupts,
	 * no parity, 8 databits, 1 stopbit, transmitter and receiver
	 * enabled), reset dlab bit:
	 */
	SerialLCR = LCR_WLS1 | LCR_WLS0;

	/* turn the receiver and transmitter back on */
	SerialIER = IER_UUE;

	return 0;
}

/* check if there is a character available to read. returns 1 if there
 * is a character available, 0 if not, and negative error number on
 * failure */
static int mhn_serial_poll(void)
{
	/* check for errors */
	if(SerialLSR & (LSR_FE | LSR_PE | LSR_OE))
		return -ESERIAL;

	if(SerialLSR & LSR_DR)
		return 1;
	else
		return 0;
}

void SerialOutputString(const char *s);
int mhn_serial_read_no_block(int *c)
{
    int rv;
    rv = mhn_serial_poll();
    if(rv <= 0)
        return rv;

    if(rv > 0)
        *c = SerialDATA & 0xff;
    return 1;
}

/* write character to serial port. return 0 on success, or negative
 * error number on failure. this function is blocking
 */
int mhn_serial_write(int c)
{
	/* wait for room in the transmit FIFO */
	while((SerialLSR & LSR_TDRQ) == 0) {
	}

	SerialDATA = c & 0xff;

	return 0;
}

void SerialInit(serial_baud_t baudrate)
{
    mhn_serial_init(baudrate);
}

void SerialOutputByte(const unsigned char c)
{
  if(c == '\n')
    SerialOutputByte('\r');

  mhn_serial_write(c);
}

/*
 * Output a single byte to the serial port.
 */
void SerialOutputRawByte(const unsigned char c)
{
    mhn_serial_write(c);
}

void SerialOutputByteInHex(const char c)
{
  char d;

  d = (char)((c >> 4) & 0x0f);
  if(d > 9)
    d += ('A' - 10);
  else
    d += '0';
  SerialOutputByte(d);

  d = (char)(c & 0x0f);
  if(d > 9)
    d += ('A' - 10);
  else
    d += '0';
  SerialOutputByte(d);
}

/*
 * Write a null terminated string to the serial port.
 */
void SerialOutputString(const char *s)
{
    while(*s != 0) {
        SerialOutputByte(*s++);
    }
}

/*
 * Write the argument of the function in hexadecimal to the serial
 * port. If you want "0x" in front of it, you'll have to add it
 * yourself.
 */
void SerialOutputHex(const u32 h)
{
    char c;
    int i;

    for(i = NIBBLES_PER_WORD - 1; i >= 0; i--) {
        c = (char)((h >> (i * 4)) & 0x0f);

        if(c > 9)
            c += ('A' - 10);
        else
            c += '0';

        SerialOutputByte(c);
    }
}

void SerialOutputSHex(const u32 h)
{
    char c;
    int i;

    for(i = NIBBLES_PER_HALFWORD - 1; i >= 0; i--) {
        c = (char)((h >> (i * 4)) & 0x0f);

        if(c > 9)
            c += ('A' - 10);
        else
            c += '0';

        SerialOutputByte(c);
    }
}

/*
 * Write the argument of the function in decimal to the serial port.
 * We just assume that each argument is positive (i.e. unsigned).
 */
void SerialOutputDec(const u32 d)
{
    int leading_zero = 1;
    u32 divisor, result, remainder;

    remainder = d;

    for(divisor = 1000000000;
        divisor > 0;
        divisor /= 10) {
        result = remainder / divisor;
        remainder %= divisor;

        if(result != 0 || divisor == 1)
            leading_zero = 0;

        if(leading_zero == 0)
            SerialOutputByte((char)(result) + '0');
    }
}

/*
 * Read a single byte from the serial port. Returns 1 on success, 0
 * otherwise. When the function is succesfull, the character read is
 * written into its argument c.
 */
int SerialInputStr(char *buf, int count)
{
  int i;
  for(i = 0; i < count; i++)
  {
    while (( SerialLSR & LSR_DR) == 0)
        msleep(1);
    buf[i] = SerialDATA & 0xff;
  }
  return(1);
} /* SerialInputByte */

/*
 * Write a block of data to the serial port. Similar to
 * SerialOutputString(), but this function just writes the number of
 * characters indicated by bufsize and doesn't look at termination
 * characters.
 */
void SerialOutputBlock(const char *buf, int bufsize)
{
    while(bufsize--)
        SerialOutputByte(*buf++);
}

/*
 * Read a single byte from the serial port. Returns 1 on success, 0
 * otherwise. When the function is succesfull, the character read is
 * written into its argument c.
 */
int SerialInputByte(char *c)
{
  unsigned int ch;

  if ((SerialLSR & LSR_DR) != 0) {
      ch = SerialLSR;

      if (ch & LSR_PE)
          SerialOutputByte('@');
      else if (ch & LSR_FE)
          SerialOutputByte('#');
      else if (ch & LSR_OE)
          SerialOutputByte('$');

      /* We currently only care about framing and parity errors */
      if ( (ch & (LSR_PE | LSR_FE) ) != 0) {
          return SerialInputByte(c);
      } else {
          *c = SerialDATA & 0xff;
          return(1);
      }
  }else { /* no bit ready */
      return(0);
  }

} /* SerialInputByte */

/*
 * read a string with maximum length len from the serial port
 * using a timeout of timeout seconds
 *
 * len is the length of array s _including_ the trailing zero,
 * the function returns the number of bytes read _excluding_
 * the trailing zero
 */
int  SerialInputString(char *s, const int len, const int timeout)
{
    u32 startTime, currentTime;
    char c;
    int i;
    int numRead;
    int skipNewline = 1;
    int maxRead = len - 1;

    startTime = TimerGetTime();

    for(numRead = 0, i = 0; numRead < maxRead;) {
        /* try to get a byte from the serial port */
        while(!SerialInputByte(&c)) {
            currentTime = TimerGetTime();

            /* check timeout value */
            if((currentTime - startTime) > timeout ) {
                /* timeout */
                s[i++] = '\0';
                return(numRead);
            }
        }
        startTime = TimerGetTime();
        /* eat newline characters at start of string */
        if((skipNewline == 1) && (c != '\r') && (c != '\n'))
            skipNewline = 0;

        if(skipNewline == 0) {
            if((c == '\r') || (c == '\n')) {
                s[i++] = '\0';
                return(numRead);
            } else {
                s[i++] = c;
                numRead++;
            }
        }
    }

    return(numRead);
}

/*
 * SerialInputBlock(): almost the same as SerialInputString(), but
 * this one just reads a block of characters without looking at
 * special characters.
 */
int  SerialInputBlock(char *buf, int bufsize, const int timeout)
{
    u32 startTime, currentTime;
    char c;
    int i;
    int numRead;
    int maxRead = bufsize;

    startTime = TimerGetTime();

    for(numRead = 0, i = 0; numRead < maxRead;) {
      /* try to get a byte from the serial port */
        while(!SerialInputByte(&c)) {
            currentTime = TimerGetTime();

            /* check timeout value */
            if((currentTime - startTime) > timeout) {
                /* timeout! */
                return(numRead);
            }
        }
//      startTime = TimerGetTime();
        buf[i++] = c;
        numRead ++;
    }

    return(numRead);
}

int SerialInputBlock_msec(char *buf, int bufsize, const int timeout_msec)
{
    char c;
    int i;
    int numRead;
    int maxRead = bufsize;
    int timeCnt = timeout_msec * 10000;

    for(numRead = 0, i = 0; numRead < maxRead;) {
      /* try to get a byte from the serial port */
        while(!SerialInputByte(&c)) {
            /* check timeout value */
            if(timeCnt-- <= 0) {
							/* timeout! */
							//return(numRead);
							return numRead;
			      }
        }
//      startTime = TimerGetTime();
        buf[i++] = c;
        numRead ++;
        timeCnt = timeout_msec * 10000;
    }

    return(numRead);
}

void SerialInputFlush(void)
{
    volatile unsigned int ch;
    while (( SerialLSR & LSR_DR) != 0) {
        if( SerialLSR & ( LSR_FE  | LSR_PE | LSR_OE ) )
                return;
        ch = SerialDATA;
    }
    return;
}

int SerialInputDWordWithTimeout()
{
    int nRet;

    if (SerialInputBlock_msec( (char *)&nRet, 4, 10) < 4) {
        return 0;
    }
    else {
        return nRet;
    }
}

void Xo_SerialOutputByte(const unsigned char c)
{
    SerialOutputByte(c);
}

int Xo_SerialInputByte(char *c)
{
    if( SerialInputByte(c))
        return 0;
    return -1;
}
